// dllwindow.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "videowindow.h"

#include <tchar.h>
#include <cassert>
#include <exception>

#include <comdef.h>
#include <log4cpp/Category.hh>

#define MYRGB(r,g,b) (((INT32)(r)) | (((INT32)(g))<<8) | (((INT32)(b))<<16)| (((INT32)(255))<<24))

#define YUVTORGB(Y,U,V,R,G,B) { \
	double r,g,b; \
	\
	r = 1.164*((float)(Y) - 16.0) + 1.596*((float)(V) - 128.0); \
	g = 1.164*((float)(Y) - 16.0) - 0.813*((float)(V) - 128.0) - 0.391*((float)(U) - 128.0); \
	b = 1.164*((float)(Y) - 16.0) + 2.018*((float)(U) - 128.0); \
	\
	r = (r > 255.0)?255.0:r; \
	g = (g > 255.0)?255.0:g; \
	b = (b > 255.0)?255.0:b; \
	r = (r < 0.0)?0.0:r; \
	g = (g < 0.0)?0.0:g; \
	b = (b < 0.0)?0.0:b; \
	\
	R = (uint8_t)r; \
	G = (uint8_t)g; \
	B = (uint8_t)b; \
				  }

static log4cpp::Category& Logger = log4cpp::Category::getInstance(__Module__Name+std::string("videowindow"));

static PTCHAR windowClassName = TEXT("librtp_video_window"); 

__declspec(thread) static librtp_video::VideoWindow* __videoWindow = NULL;

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	assert(NULL != __videoWindow);
	return __videoWindow->wndProc(hWnd,message,wParam,lParam);
}

static ATOM RegisterWindowClass()
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= NULL;
	wcex.hIcon			= NULL;
	wcex.hCursor		= NULL;
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= windowClassName;
	wcex.hIconSm		= NULL;

	return RegisterClassEx(&wcex);
}

namespace librtp_video{

/************************************************************************
 Helper class used to init

************************************************************************/
class InitHelper{
public:
	InitHelper(){
		RegisterWindowClass();
	}
};

// This is the constructor of a class that has been exported.
// see dllwindow.h for the class definition
VideoWindow::VideoWindow()
{
	// make sure the window class would be registered before we create
	static InitHelper helper;

	m_sourceID = 0;
	m_hwnd = NULL;

	m_width = 320;
	m_height = 240;
	m_pImgData = NULL;
	m_pDrawBuffer = NULL;
	m_pBitArray = NULL;

	m_resize = false;

	if(m_frameMutex.Init() < 0){
		throw std::exception();
	}
}

VideoWindow::~VideoWindow()
{
	destroyWindow();
	delete []m_pImgData;
	delete []m_pDrawBuffer;
	delete []m_pBitArray;
}

void VideoWindow::processRawVideoMessage(MIPRawYUV420PVideoMessage *pMsg)
{

	m_frameMutex.Lock();
	// need to resize
	if (NULL == m_pImgData || m_width != pMsg->getWidth() || m_height != pMsg->getHeight()) 
	{
		m_width = pMsg->getWidth();
		m_height = pMsg->getHeight();
		m_resize = true;
		delete []m_pImgData;
		m_pImgData = new uint8_t[m_width*m_height*3/2];
	}

	assert(NULL != m_pImgData);

	memcpy(m_pImgData,pMsg->getImageData(),m_width*m_height*3/2);
	m_frameMutex.Unlock();
}

// Draw Picture here
LRESULT VideoWindow::onTimer(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
	
	int width,height;

	// copy data from buffer
	m_frameMutex.Lock();

	if(m_resize){
		m_resize = false;

		delete []m_pDrawBuffer;
		m_pDrawBuffer = new uint8_t[m_width*m_height*3/2];		

		delete[]m_pBitArray;
		m_pBitArray = new uint32_t[m_width*m_height];
	}

	width = m_width;
	height = m_height;

	// copy image data from ImgData to DrawBuffer
	if(NULL != m_pImgData && NULL != m_pDrawBuffer){
		memcpy(m_pDrawBuffer,m_pImgData,m_width*m_height*3/2);
	}

	m_frameMutex.Unlock();

	// Draw the picture in the window
	HDC hdc = GetDC(hWnd);

	if (NULL != m_pDrawBuffer)
	{
		const uint8_t *pValues = m_pDrawBuffer;
		size_t totalNum = m_width*m_height;
		const uint8_t *pY = pValues;
		const uint8_t *pU = pValues+totalNum;
		const uint8_t *pV = pValues+totalNum+totalNum/4;

		int indexY = 0;
		int indexU = 0;
		int indexV = 0;

		for (int y = 0 ; y < m_height ; y += 2)
		{
			for (int x = 0 ; x < m_width ; x += 2)
			{
				uint8_t Y0 = pY[indexY + 0];
				uint8_t Y1 = pY[indexY + 1];
				uint8_t Y2 = pY[indexY + m_width];
				uint8_t Y3 = pY[indexY + m_width + 1];
				uint8_t U = pU[indexU];
				uint8_t V = pV[indexV];

				uint8_t R,G,B;

				YUVTORGB(Y0,U,V,R,G,B);

//				SetPixel(hMemDC,x, y, RGB(R, G, B));	
				m_pBitArray[y * width + x] = RGB(R, G, B);


				YUVTORGB(Y1,U,V,R,G,B);
//				SetPixel(hMemDC,x + 1, y, RGB(R, G, B));
				m_pBitArray[y * width + x + 1] = RGB(R, G, B);


				YUVTORGB(Y2,U,V,R,G,B);
//				SetPixel(hMemDC,x, y + 1, RGB(R, G, B));
				m_pBitArray[(y + 1) * width + x] = RGB(R, G, B);


				YUVTORGB(Y3,U,V,R,G,B);
//				SetPixel(hMemDC,x + 1, y + 1, RGB(R, G, B));
				m_pBitArray[(y + 1) * width + x + 1] = RGB(R, G, B);

				indexY += 2;
				indexU++;
				indexV++;
			}
			indexY += m_width;
		}
	}

	HBITMAP hBitmap = CreateCompatibleBitmap(hdc,width,height);
	SetBitmapBits(hBitmap,width*height*sizeof(INT32),m_pBitArray);
	HDC hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC,hBitmap);

	RECT windowRect;
	GetWindowRect(m_hwnd,&windowRect);

	StretchBlt(hdc,0,0,windowRect.right - windowRect.left,windowRect.bottom - windowRect.top,hMemDC,
		0,0,width,height,SRCCOPY);
	
	DeleteObject(hMemDC);
	DeleteObject(hBitmap);
	ReleaseDC(hWnd,hdc);

	return 0;
}


LRESULT VideoWindow::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){

	switch (message)
	{
	case WM_DESTROY:
		KillTimer(m_hwnd,1);
		PostQuitMessage(0);
		break;

/*
	// do resize the window
	case WM_SIZE:
		RECT rect;
		GetWindowRect(m_hwnd,&rect);
		MoveWindow(m_hwnd,rect.left,rect.top,m_width,m_height,TRUE);
		break;
*/

	case WM_TIMER:
		onTimer(hWnd,message,wParam,lParam);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

BOOL VideoWindow::createWindow(){

	m_hwnd = CreateWindow(windowClassName, TEXT(""), WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, m_width, m_height, NULL, NULL, NULL, NULL);

	if (!m_hwnd)
	{
		return FALSE;
	}

	SetTimer(m_hwnd,1,100,NULL);

	ShowWindow(m_hwnd, SW_SHOW);
	UpdateWindow(m_hwnd);

	return TRUE;
}

void VideoWindow::destroyWindow(){

	if(NULL != m_hwnd){
		SendMessage(m_hwnd,WM_DESTROY,0,0);
		m_hwnd = NULL;
	}
}

void VideoWindow::setWindowStyle(LONG style){
	if(NULL != m_hwnd){
		SetWindowLong(m_hwnd,GWL_STYLE,style);
	}
}

LONG VideoWindow::getWindowStyle(){
	if(NULL != m_hwnd){
		return GetWindowLong(m_hwnd,GWL_STYLE);
	}else{
		return 0;
	}
}


void VideoWindow::setParent(HWND parent){
	if(NULL != m_hwnd){
		SetParent(m_hwnd,parent);
	}
}

void* VideoWindow::Thread(){

	Logger.debug("VideoWindow Thread Start");

	MSG msg;

	__videoWindow = this;

	createWindow();

	if(!m_hwnd){
		return NULL;
	}

	JThread::ThreadStarted();

	while (0 != GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);

		if(WM_QUIT == msg.message){
			break;
		}

		DispatchMessage(&msg);
	}

	__videoWindow->m_hwnd = NULL;
	__videoWindow = NULL;

	Logger.debug("VideoWindow Thread Exit");

	return NULL;
}

}

